package com.vdimitr.katapotter;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * Calculates the lowest price of a cart containing books.
 * 
 * @author vasileios.dimitriadis
 * 
 */
public class Cashier {

	private BucketPriceCalculator calculator = new BucketPriceCalculator();

	public double getPrice(Cart cart) {
		if (cart.isEmpty()) {
			return 0.0;
		} else {
			/*
			 * We create buckets books. To find the minimum price, the book is
			 * placed in each possible bucket that is not already contained and
			 * the total price of all buckets is calculated. Based on the
			 * minimum total price the bucket is selected to place the book in
			 * and the total price is returned.
			 */
			List<Bucket> buckets = new LinkedList<Bucket>();

			for (Book book : cart.getBooks()) {
				if (thereAreNoBuckets(buckets)) {
					addBookToANewBucket(book, buckets);
				} else {
					greedySearchForBucketToAddBook(book, buckets);
				}
			}
			return getCurrentPrice(buckets);
		}
	}

	private void greedySearchForBucketToAddBook(Book book, List<Bucket> buckets) {
		List<CandidateBucket> candidateBuckets = calculateCandidateBucketsToAddBook(
				book, buckets);

		decideInWhichBucketToAddBook(book, buckets, candidateBuckets);
	}

	private void decideInWhichBucketToAddBook(Book book, List<Bucket> buckets,
			List<CandidateBucket> candidateBuckets) {
		if (thereAreNoCandidateBuckets(candidateBuckets)) {
			addBookToANewBucket(book, buckets);
		} else {
			addBookToBucketWithMinimumPrice(book, candidateBuckets);
		}
	}

	private List<CandidateBucket> calculateCandidateBucketsToAddBook(Book book,
			List<Bucket> buckets) {
		List<CandidateBucket> candidateBuckets = new LinkedList<CandidateBucket>();
		for (Bucket existingBucket : buckets) {
			if (bookNotInBucket(book, existingBucket)) {
				addBookToBucketAndCalculatePrice(book, buckets,
						candidateBuckets, existingBucket);
			}
		}
		return candidateBuckets;
	}

	private void addBookToBucketAndCalculatePrice(Book book,
			List<Bucket> buckets, List<CandidateBucket> candidateBuckets,
			Bucket existingBucket) {
		existingBucket.add(book);
		CandidateBucket candidateBucket = new CandidateBucket();
		candidateBucket.setBucket(existingBucket);
		candidateBucket.setTotalPrice(getCurrentPrice(buckets));
		candidateBuckets.add(candidateBucket);
		existingBucket.remove(book);
	}

	private boolean bookNotInBucket(Book book, Bucket bucket) {
		return !bucket.containsBook(book);
	}

	private void addBookToBucketWithMinimumPrice(Book book,
			List<CandidateBucket> candidateBuckets) {
		Bucket bucketToPlaceIn = findBucketWithMinimumPrice(candidateBuckets);
		bucketToPlaceIn.add(book);
	}

	private boolean thereAreNoCandidateBuckets(
			List<CandidateBucket> candidateBuckets) {
		return candidateBuckets.isEmpty();
	}

	private boolean thereAreNoBuckets(List<Bucket> buckets) {
		return buckets.isEmpty();
	}

	private void addBookToANewBucket(Book book, List<Bucket> buckets) {
		Bucket bucketToPlaceIn = new Bucket();
		bucketToPlaceIn.add(book);
		buckets.add(bucketToPlaceIn);
	}

	private Bucket findBucketWithMinimumPrice(
			List<CandidateBucket> candidateBuckets) {
		return Collections.min(candidateBuckets,
				new CandidateBucketTotalPriceComparator()).getBucket();
	}

	private double getCurrentPrice(List<Bucket> buckets) {
		double price = 0.0;
		for (Bucket bucket : buckets) {
			price += calculator.getPrice(bucket.numberOfBooks());
		}
		return price;
	}
}
